/**
 * Copyright (c) 2016 NumberFour AG.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   NumberFour AG - Initial API and implementation
 */
package org.eclipse.n4js.transpiler.es.tests;

import static org.eclipse.xtext.xbase.lib.IterableExtensions.toList;
import static org.junit.Assert.assertEquals;

import java.math.BigDecimal;
import java.util.List;

import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.n4js.N4JSInjectorProvider;
import org.eclipse.n4js.n4JS.ArrowFunction;
import org.eclipse.n4js.n4JS.ExpressionStatement;
import org.eclipse.n4js.n4JS.FunctionExpression;
import org.eclipse.n4js.n4JS.IntLiteral;
import org.eclipse.n4js.n4JS.ParameterizedCallExpression;
import org.eclipse.n4js.n4JS.ReturnStatement;
import org.eclipse.n4js.n4JS.Script;
import org.eclipse.n4js.tests.helper.mock.MockWorkspaceSupplier;
import org.eclipse.n4js.transpiler.TranspilerState;
import org.eclipse.n4js.transpiler.im.ParameterizedPropertyAccessExpression_IM;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.testing.InjectWith;
import org.eclipse.xtext.testing.XtextRunner;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;

@RunWith(XtextRunner.class)
@InjectWith(N4JSInjectorProvider.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ArrowFunctionTest extends AbstractTranspilerTest {

	@Test
	public void testWrapImplicitReturn() {

		TranspilerState state = transform(createTranspilerState("""
				()=>5
				"""));

		ArrowFunction F_AST = findFirstInAST(state, ArrowFunction.class);
		List<FunctionExpression> allFEs = toList(EcoreUtil2.eAllOfType(state.im, FunctionExpression.class));

		// precondition:
		assertEquals("Should have 0 parameters", 0, F_AST.getFpars().size());
		assertEquals("Should have 1 statement", 1, F_AST.getBody().getStatements().size());
		ExpressionStatement exprStmt = (ExpressionStatement) F_AST.getBody().getStatements().get(0);
		IntLiteral intLit = (IntLiteral) exprStmt.getExpression();
		assertEquals("statement should be int (5)", BigDecimal.valueOf(5L), intLit.getValue());

		// After Transpilations:

		assertEquals("Exactly 1 FunctionExpression expected.", 1, allFEs.size());
		FunctionExpression F_IM = allFEs.get(0);

		assertEquals("Should have 0 parameters", 0, F_IM.getFpars().size());
		assertEquals("Should have 1 statement", 1, F_IM.getBody().getStatements().size());
		ReturnStatement retStmt_IM = (ReturnStatement) F_IM.getBody().getStatements().get(0);
		IntLiteral intLit_IM = (IntLiteral) retStmt_IM.getExpression();
		assertEquals("statement should be int (5)", BigDecimal.valueOf(5L), intLit_IM.getValue());

		// .System x2 , .exports, .register , .bind expected :
		List<ParameterizedCallExpression> allCallExpressions = toList(EcoreUtil2.eAllOfType(state.im,
				ParameterizedCallExpression.class));
		assertEquals("expect 1 call-expression, second should be '.bind()' ", 1, allCallExpressions.size());

		ParameterizedCallExpression bindCall_IM = allCallExpressions.get(0);
		ParameterizedPropertyAccessExpression_IM methodRef_IM = (ParameterizedPropertyAccessExpression_IM) bindCall_IM
				.getTarget();
		assertEquals("bind", methodRef_IM.getRewiredTarget().getName());
	}

	@Test
	public void test_Compile() throws Throwable {

		String script = """
					class C {

						private _data: number = -1;

						argsTotal: number = 0;

						public get data() {
					  		if (this._data==-1) {
					  			(() => { this._data = 1; })()
							}
							(()=>{ this.argsTotal += arguments.length })()
							return this._data;
						}

						public set data(data: number) {
							(() => { this._data = data; })();
							(()=>{ this.argsTotal += arguments.length })()
							this.notifyListeners();
						}

						notifyListeners(): void {
						}

					}
				""";

		String moduleWrapped = """
					// Generated by N4JS transpiler; for copyright see original N4JS source file.

					import 'n4js-runtime'

					class C extends N4Object {
						constructor() {
							super();
							this._data = -1;
							this.argsTotal = 0;
						}
						get data() {
							var $capturedArgs = arguments;
							if (this._data == -1) {
								((function() {
									this._data = 1;
								}).bind(this))();
							}
							((function() {
								this.argsTotal += $capturedArgs.length;
							}).bind(this))();
							return this._data;
						}
						set data(data) {
							var $capturedArgs = arguments;
							((function() {
								this._data = data;
							}).bind(this))();
							((function() {
								this.argsTotal += $capturedArgs.length;
							}).bind(this))();
							this.notifyListeners();
						}
						notifyListeners() {}
						static get n4type() {
							return $getReflectionForClass(this,'["C","A","%s",["f._data","f.argsTotal"]]');
						}
					}
				""".formatted(MockWorkspaceSupplier.TEST_PROJECT__NAME);

		// Prepare ResourceSet to contain exportedScript:
		ResourceSet resSet = installExportedScript();

		Script scriptNode = parseHelper.parse(script, toTestProjectURI("A.n4js"), resSet);
		resolveLazyRefs(scriptNode);

		assertCompileResult(scriptNode, moduleWrapped);
	}

}
